package com.soos.utils;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * Created with IntelliJ IDEA.
 * Description: 简单的redis分布式锁
 * User: sfx
 * Date: 2023-06-28
 * Time: 21:50
 */
public class SimpleRedisLock implements ILOCK{
    /**
     * 分布式锁实现逻辑
     * 1. 获取锁
     *    set nx ex   (ex确保锁一定被释放) (nx实现互斥效果)
     *    同时我们的锁实现是非阻塞式的 (获取锁失败后,立即放弃)
     * 2. 释放锁
     * 使用delete把锁删除就可以了
     */
    //业务名
    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX = "lock:";

    //使用lua脚本保证释放锁的->多条指令的原子性
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    //初始化lua脚本
    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
    
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long timeout) {
        //解决分布式锁误删问题-->存放线程标识,当释放锁的时候检查线程标识即可
        //这里我们使用 UUID + 线程id 当做线程标识
        //UUID 用来区分不同的JVM ,线程id用于区分同一个JVM的线程->这样就可以唯一确定一个线程了
        //线程标识
        String curThreadMark = ID_PREFIX + Thread.currentThread().getId();
        Boolean isSuccess = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, curThreadMark, timeout, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(isSuccess);
    }

    /**
     * 使用lua脚本保证释放锁的原子性
     */
    @Override
    public void unlock() {
        stringRedisTemplate.execute(UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId());
    }
//    @Override
//    public void unlock() {
//        //获取当前线程标识
//        String curThreadMark = ID_PREFIX + Thread.currentThread().getId();
//        //获取锁标识
//        String lockMark = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
//        if(curThreadMark.equals(lockMark)) {
//            stringRedisTemplate.delete(KEY_PREFIX + name);
//        }
//    }
}